﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TasksModel
{
    /// <summary>
    /// All dates are in specified time zone.
    /// </summary>
    /// <remarks>By default, all dates are in UTC. TimeZone is applied for display only.</remarks>
    [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://mp.tasks.com/2010/")]
    public class Task : NamedObject, IComparable<Task>
    {
        #region Properties

        private string _richContent;

        /// <summary>
        /// Serialized in Content property.
        /// </summary>
        [System.Xml.Serialization.XmlIgnore]
        public string RichContent
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _richContent; }
            set
            {
                if (!object.ReferenceEquals(_richContent, value))
                {
                    _richContent = value;
                    NotifyPropertyChanged("RichContent");
                }
            }
        }

        /// <summary>
        /// Only for serialization, do not use manually.
        /// </summary>
        [System.Xml.Serialization.XmlElement("RichContent")]
        public System.Xml.XmlLinkedNode Content
        {
            get
            {
                System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                return doc.CreateCDataSection(_richContent);
            }
            set
            {
                if (value == null)
                    _richContent = null;
                else
                    _richContent = value.Value;
            }
        }

        private TaskAuthor _createdBy;

        /// <summary>
        /// Reference only, don't change content.
        /// </summary>
        public TaskAuthor CreatedBy
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdBy; }
            set
            {
                if (!object.ReferenceEquals(_createdBy, value))
                {
                    _createdBy = value;
                    NotifyPropertyChanged("CreatedBy");
                }
            }
        }

        private string _tzInfo;

        /// <summary>
        /// Value as for Id in TimeZoneInfo class.
        /// Defaults to Local time zone at the moment of instance creation.
        /// Used by Local* properties to provide times in local time zone of the task.
        /// </summary>
        public string TimeZone
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _tzInfo; }
            set
            {
                if (!object.ReferenceEquals(_tzInfo, value))
                {
                    if (value == null)
                        throw new ArgumentNullException();
                    _tzInfo = value;
                    NotifyPropertyChanged("TimeZone");
                }
            }
        }

        private DateTime _createdAt;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime CreatedAt
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdAt; }
            set
            {
                value = CutMilliseconds(value);
                if (_createdAt != value)
                {
                    _createdAt = value;
                    NotifyPropertyChanged("CreatedAt");
                }
            }
        }

        private DateTime? _changedAt;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? ChangedAt
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _changedAt; }
            set
            {
                value = CutMilliseconds(value);
                if (_changedAt != value)
                {
                    _changedAt = value;
                    NotifyPropertyChanged("ChangedAt");
                }
            }
        }

        private DateTime? _dueDate;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? DueDate
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _dueDate; }
            set
            {
                value = CutMilliseconds(value);
                if (_dueDate != value)
                {
                    _dueDate = value;
                    NotifyPropertyChanged("DueDate");
                }
            }
        }

        private DateTime? _finishedAt;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? FinishedAt
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _finishedAt; }
            set
            {
                value = CutMilliseconds(value);
                if (_finishedAt != value)
                {
                    _finishedAt = value;
                    NotifyPropertyChanged("FinishedAt");
                }
            }
        }

        private bool _allDay;

        public bool AllDay
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _allDay; }
            set
            {
                if (_allDay != value)
                {
                    _allDay = value;
                    NotifyPropertyChanged("AllDay");
                }
            }
        }

        private bool _reocurring;

        public bool Reocurring
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _reocurring; }
            set
            {
                if (_reocurring != value)
                {
                    _reocurring = value;
                    NotifyPropertyChanged("Reocurring");
                }
            }
        }

        private TaskRecurrence _recurrenceDetails;

        /// <summary>
        /// Only valid if Reoccuring is true.
        /// </summary>
        public TaskRecurrence RecurrenceDetails
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _recurrenceDetails; }
            set
            {
                if (!object.ReferenceEquals(_recurrenceDetails, value))
                {
                    _recurrenceDetails = value;
                    NotifyPropertyChanged("RecurrenceDetails");
                }
            }
        }

        private bool _private;

        public bool Private
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _private; }
            set
            {
                if (_private != value)
                {
                    _private = value;
                    NotifyPropertyChanged("Private");
                }
            }
        }

        private bool _archived;

        public bool Archived
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _archived; }
            set
            {
                if (_archived != value)
                {
                    _archived = value;
                    NotifyPropertyChanged("Archived");
                }
            }
        }

        private bool _noSynch;

        /// <summary>
        /// If true, item is not synchronized with external source (like Google).
        /// </summary>
        public bool NoSynch
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _noSynch; }
            set
            {
                if (_noSynch != value)
                {
                    _noSynch = value;
                    NotifyPropertyChanged("NoSynch");
                }
            }
        }

        private string _extReference;

        /// <summary>
        /// Any additional reference for external source.
        /// May be used by synchronization.
        /// Not editable by user.
        /// </summary>
        public string ExternalReference
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _extReference; }
            set
            {
                if (!object.ReferenceEquals(_extReference, value))
                {
                    _extReference = value;
                    NotifyPropertyChanged("ExternalReference");
                }
            }
        }

        private string _url;

        /// <summary>
        /// User provided url reference.
        /// </summary>
        public string Url
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _url; }
            set
            {
                if (!object.ReferenceEquals(_url, value))
                {
                    _url = value;
                    NotifyPropertyChanged("Url");
                }
            }
        }

        private string _location;

        /// <summary>
        /// Physical location of event.
        /// </summary>
        public string Location
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _location; }
            set
            {
                if (!object.ReferenceEquals(_location, value))
                {
                    _location = value;
                    NotifyPropertyChanged("Location");
                }
            }
        }

        private PriorityClass _priority;

        public PriorityClass Priority
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _priority; }
            set
            {
                if (_priority != value)
                {
                    _priority = value;
                    NotifyPropertyChanged("Priority");
                }
            }
        }

        private TaskClass _class;

        /// <summary>
        /// Not displayed.
        /// </summary>
        public TaskClass Class
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _class; }
            set
            {
                if (_class != value)
                {
                    _class = value;
                    NotifyPropertyChanged("Class");
                }
            }
        }

        private TaskStatus _status;

        /// <summary>
        /// Reference only, don't change content.
        /// </summary>
        public TaskStatus Status
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _status; }
            set
            {
                if (!object.ReferenceEquals(_status, value))
                {
                    _status = value;
                    NotifyPropertyChanged("Status");
                }
            }
        }

        private int _order;

        /// <summary>
        /// In a containing list, 0 based.
        /// </summary>
        public int Order
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _order; }
            set
            {
                if (_order != value)
                {
                    if (value < 0)
                        throw new ArgumentOutOfRangeException();
                    _order = value;
                    NotifyPropertyChanged("Order");
                }
            }
        }

        private int _progress;

        /// <summary>
        /// Percent 0-100
        /// </summary>
        public int Progress
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _progress; }
            set
            {
                if (_progress != value)
                {
                    if (value < 0)
                        throw new ArgumentOutOfRangeException();
                    _progress = value;
                    NotifyPropertyChanged("Progress");
                }
            }
        }

        private int _estimatedEffort;

        /// <summary>
        /// In hours.
        /// </summary>
        public int EstimatedEffort
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _estimatedEffort; }
            set
            {
                if (_estimatedEffort != value)
                {
                    if (value < 0)
                        throw new ArgumentOutOfRangeException();
                    _estimatedEffort = value;
                    NotifyPropertyChanged("EstimatedEffort");
                }
            }
        }

        private TaskFlagList _flags;

        /// <summary>
        /// Not null. Subset of Library.Flags
        /// </summary>
        public TaskFlagList Flags
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _flags; }
            set
            {
                if (!object.ReferenceEquals(_flags, value))
                {
                    _flags = value;
                    NotifyPropertyChanged("Flags");
                }
            }
        }

        private TaskTagList _tags;

        /// <summary>
        /// Not null.
        /// </summary>
        public TaskTagList Tags
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _tags; }
            set
            {
                if (!object.ReferenceEquals(_tags, value))
                {
                    _tags = value;
                    NotifyPropertyChanged("Tags");
                }
            }
        }

        private TaskAttachmentList _attachments;

        /// <summary>
        /// Not null.
        /// </summary>
        public TaskAttachmentList Attachments
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _attachments; }
            set
            {
                if (!object.ReferenceEquals(_attachments, value))
                {
                    _attachments = value;
                    NotifyPropertyChanged("Attachments");
                }
            }
        }

        private TaskList _dependencies;

        /// <summary>
        /// Reference list only, don't change content of items. Not null.
        /// </summary>
        public TaskList Dependencies
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _dependencies; }
            set
            {
                if (!object.ReferenceEquals(_dependencies, value))
                {
                    _dependencies = value;
                    NotifyPropertyChanged("Dependencies");
                }
            }
        }

        public string UriSchema
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return BaseSchema + "task"; }
        }

        #endregion

        #region Constructors

        public Task(Guid id)
        {
            ID = id;
            _attachments = new TaskAttachmentList();
            _dependencies = new TaskList();
            _flags = new TaskFlagList();
            _tags = new TaskTagList();
            SetLocalTimeZone();
        }

        public Task()
            : this(Guid.NewGuid())
        {
        }

        public Task(Task task)
            : this(task.ID)
        {
            // create a deep copy
            if (task._attachments != null)
                foreach (TaskAttachment a in task._attachments)
                    _attachments.Add(new TaskAttachment(a));
            if (task._dependencies != null)
                foreach (Task t in task._dependencies)
                    _dependencies.Add(t);
            if (task._flags != null)
                foreach (TaskFlag f in task._flags)
                    _flags.Add(new TaskFlag(f));
            if (task._recurrenceDetails != null)
                _recurrenceDetails = new TaskRecurrence(task._recurrenceDetails);
            if (task._tags != null)
                _tags = new TaskTagList(task._tags);
            _archived = task._archived;
            _class = task._class;
            _createdAt = task._createdAt;
            _createdBy = task._createdBy;
            _dueDate = task._dueDate;
            _estimatedEffort = task._estimatedEffort;
            _finishedAt = task._finishedAt;
            _order = task._order;
            _priority = task._priority;
            _progress = task._progress;
            _richContent = task._richContent;
            _private = task._private;
            _reocurring = task._reocurring;
            _location = task._location;
            _noSynch = task._noSynch;
            _tzInfo = task._tzInfo;
            _url = task._url;
            Name = task.Name;
            Timestamp = task.Timestamp;
        }

        #endregion

        public void MarkCompleted()
        {
            SuspendChangedEvent();
            ChangedAt = FinishedAt = ChangedAt = DateTime.UtcNow;
            ResumeChangedEvent();
            Progress = 100;
        }

        public void SetLocalTimeZone()
        {
            _tzInfo = TimeZoneInfo.Local.Id;
        }

        #region Local dates

        [System.Xml.Serialization.XmlIgnore]
        public DateTime LocalCreatedAt
        {
            get
            {
                return DateTimeTz.ToLocal(CreatedAt, _tzInfo);
            }
        }

        [System.Xml.Serialization.XmlIgnore]
        public DateTime? LocalChangedAt
        {
            get
            {
                if (!ChangedAt.HasValue) return null;
                return DateTimeTz.ToLocal(ChangedAt.Value, _tzInfo);
            }
        }

        [System.Xml.Serialization.XmlIgnore]
        public DateTime? LocalDueDate
        {
            get
            {
                if (!DueDate.HasValue) return null;
                return DateTimeTz.ToLocal(DueDate.Value, _tzInfo);
            }
        }

        [System.Xml.Serialization.XmlIgnore]
        public DateTime? LocalFinishedAt
        {
            get
            {
                if (!FinishedAt.HasValue) return null;
                return DateTimeTz.ToLocal(FinishedAt.Value, _tzInfo);
            }
        }

        #endregion

        /// <summary>
        /// IComparable default sort order by Name
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(Task other)
        {
            return string.Compare(Name, other.Name, StringComparison.CurrentCultureIgnoreCase);
        }
    }
}